module FLEXINVERT

using LinearAlgebra
using Dates

# Include modules in dependency order to avoid circular dependencies
# 1. Configuration and constants first
include("config/constants.jl")
include("config/settings.jl")

# 2. Utilities and core types
include("utils/tools.jl")
include("utils/dates.jl")
include("utils/timing.jl")
include("core/types.jl")

# 3. Core observation types (must come before modules that use them)
include("core/observations.jl")

# 4. I/O modules
include("io/netcdf.jl")
include("io/output_writer.jl")
include("io/receptors.jl")
include("io/observations.jl")

# 5. Core domain and regions
include("core/regions.jl")

# 6. Transport modules
include("transport/footprints.jl")
include("transport/initial_conditions.jl")
include("transport/initialize.jl")

# 7. Forward model (needs to come before modules that use PriorFluxes)
include("core/forward_model.jl")

# 8. Prior I/O (needs forward model types)
include("io/prior.jl")

# 9. Covariance matrices (needs to come before transformations)
include("covariance/prior_covariance.jl")

# 10. Transformations (needs covariance types)
include("utils/transformations.jl")

# 11. State vector (needs transformations and covariance)
include("core/state_vector.jl")

# 12. Optimization modules
include("optimization/conjugate_gradient.jl")
include("optimization/quasi_newton.jl")

# 13. MCMC modules (needs posterior evaluation infrastructure)
include("mcmc/types.jl")
include("mcmc/posterior.jl")
include("mcmc/mala.jl")
include("mcmc/variables.jl")
include("mcmc/sampler.jl")
include("mcmc/integration.jl")

# 14. Inversion core (needs all other modules)
include("core/inversion.jl")

# 15. Timed run function with GPTL-style instrumentation
include("timed_run.jl")

using .Settings: Files, Config, read_file_settings, read_config_settings
using .CoreTypes: Domain
using .ObservationsCore: Observations, empty_observations, summary_stats, apply_measurement_error_floor!
using .Regions: get_regions!
using .NetCDFIO
using .OutputWriter
using .Receptors: read_reclist, Receptor
using .ObservationsIO: read_observations
using .Footprints: average_footprints!, load_observation_footprints
using .InitialConditions: compute_initial_contributions!
using .TransportInit: initialize_transport_system
using .ForwardModel: forward_model, PriorFluxes
using .PriorIO: read_prior_fluxes
using .PriorCovariance: build_prior_covariance, CovarianceMatrix
using .Transformations: validate_transforms
using .StateVector: InversionState, initialize_state
using .ConjugateGradient: congrad!
using .QuasiNewton: m1qn3!
using .MCMCTypes: MCMCState, MCMCConfiguration, ProposalType
using .Posterior: LogPosteriorEvaluator
using .MALA: MALAProposal, MALAOptions
using .Sampler: MCMCSampler
using .MCMCIntegration: run_mcmc_inversion, create_mcmc_evaluator, mcmc_configuration_from_config
using .Inversion: invert!, InversionResult, save_optimization_logs
using .Timing: @timed_function, print_timing_report, reset_timing!, with_timing

export Files, Config, Domain, get_regions!, read_file_settings, read_config_settings, average_footprints!
const ProposalMALA = MCMCTypes.MALA

export MCMCState, MCMCConfiguration, ProposalType, ProposalMALA, LogPosteriorEvaluator
export MALAProposal, MALAOptions, MCMCSampler
export run_mcmc_inversion, create_mcmc_evaluator, mcmc_configuration_from_config
export run_timed, print_timing_report

"""
    run(settings_files::AbstractString, settings_config::AbstractString)

Entry point similar to the Fortran `program main`. Reads settings, initializes
core domain variables, and computes grid-region mappings. This is an initial
port; many downstream components are stubbed.
"""
function run(settings_files::AbstractString, settings_config::AbstractString)
    files = read_file_settings(settings_files)
    config = read_config_settings(settings_config)

    # Initialize Domain from config – in Fortran this was in initialize.f90.
    domain = Domain(config)

    # Initialize transport system (FLEXPART grids and validation)
    grids, date_info = try
        initialize_transport_system(files, config, domain)
    catch err
        @warn "Transport initialization failed; continuing with limited functionality" err
        (nothing, nothing)
    end

    # Optional: read land-sea mask prior to region building
    if !isempty(files.file_lsm) && !isempty(files.varname_lsm)
        try
            NetCDFIO.read_lsm!(domain, files.file_lsm, files.varname_lsm, files.lonname_lsm, files.latname_lsm)
        catch err
            @warn "Failed to read LSM; proceeding with default all-land mask" err
        end
    end

    # Compute regions and derived quantities
    get_regions!(domain, files, config)

    # Optional: read receptors and observations if files exist
    receptors = Vector{Receptor}()
    obs = empty_observations()
    footprints = nothing
    if !isempty(files.file_recept) && isfile(files.file_recept)
        receptors = read_reclist(files.file_recept)
        @info "Loaded $(length(receptors)) receptors from $(files.file_recept)"
    end
    if !isempty(files.path_obs) && isdir(files.path_obs) && !isempty(files.suffix)
        obs = read_observations(files.path_obs, files.suffix; datei=config.datei, datef=config.datef)

        if config.measerr > 0
            apply_measurement_error_floor!(obs, config.measerr)
        end

        stats = summary_stats(obs)
        @info "Observation summary: $(stats.count) observations from $(stats.n_stations) stations"
        @info "Date range: $(stats.date_range[1]) to $(stats.date_range[2])"
        @info "Concentration range: $(round(stats.concentration_range[1], digits=2)) to $(round(stats.concentration_range[2], digits=2))"

        # Perform footprint averaging if enabled
        if config.average_fp
            try
                average_footprints!(obs, config, files)
                @info "Footprint averaging completed successfully"
            catch err
                @warn "Footprint averaging failed" err
            end
        end

        # Run inversion if observations are available and method is specified
        if length(obs.concentrations) > 0
            try
                prior_fluxes = read_prior_fluxes(files, config, domain)
                footprints = load_observation_footprints(files, config, domain, obs)
                y_mod = forward_model(config, domain, prior_fluxes, obs;
                                      footprints=footprints, files=files)
                @info "Forward model test: computed $(length(y_mod)) modeled concentrations"
                @info "Model range: $(round(minimum(y_mod), digits=2)) to $(round(maximum(y_mod), digits=2))"

                # Run inversion if method is specified (not just forward model test)
                if config.method in ["congrad", "m1qn3", "analytic", "mcmc"]
                    @info "Starting inversion with method: $(config.method)"

                    # Build prior covariance matrix
                    covariance = build_prior_covariance(config, domain, prior_fluxes)
                    @info "Built prior covariance matrix: $(covariance.full_matrix === nothing ? (covariance.nbox, covariance.nbox) : size(covariance.full_matrix)) (modes: $(covariance.n_modes))"

                    # Initialize state vector
                    state = initialize_state(config, domain, covariance)
                    @info "Initialized state vector with $(state.n_control) variables"

                    # Run inversion
                    inversion_result = invert!(state, prior_fluxes, obs, covariance, config, files, domain, footprints)

                    @info "Inversion completed successfully"
                    @info "Method: $(inversion_result.method)"
                    @info "Convergence: $(inversion_result.convergence_info)"
                    @info "Final cost: $(round(inversion_result.final_cost, digits=4))"

                    # Save inversion results
                    if !isempty(files.path_output)
                        try
                            save_optimization_logs(inversion_result, files)
                        catch err
                            @warn "Failed to save inversion results" err
                        end
                    end

                    # Return extended results including inversion
                    return (; files, config, domain, receptors, obs, grids, date_info,
                            prior_fluxes, covariance, state, inversion_result, footprints)
                end

            catch err
                @warn "Forward model test or inversion failed" err
            end
        end
    end

    # Save mapping products similar to Fortran output
    if !isempty(files.path_output)
        try
            OutputWriter.save_region_products(files.path_output, domain)
        catch err
            @warn "Failed to save region products" err
        end
    end

    return (; files, config, domain, receptors, obs, grids, date_info, footprints)
end

end # module
